package org.droid.cmplatform.core.task;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.droid.cmplatform.module.AbsTask;
import org.droid.cmplatform.module.AbsTask.TaskStatus;
import org.droid.cmplatform.utils.debug.L;

import android.os.Looper;
import android.os.Process;

/**
 * 面向Android系统的任务驱动引擎
 * 
 * @author Karen
 * 
 */
public class TaskMain {
    private static final String TAG = "TaskMain";

    private static final String THREADID_DEFAULT = "taskmain";

    private static final int CORE_POOL_SIZE = 2;
    private static final int MAXIMUM_POOL_SIZE = 128;
    private static final int KEEP_ALIVE = 5;

    /**
     * 任务线程池
     */
    private BlockingQueue<Runnable> sWorkQueue = null;
    private ThreadFactory sThreadFactory = null;
    private ThreadPoolExecutor sExecutor = null;

    /**
     * 添加任务的超时时间
     */
    private static final int TIMEOUT_ADD_TASK = 2;

    private String threadId = "";// 线程ID

    private static TaskMain INSTANCE = null;
    private static volatile Object lockInstance = new Object();

    /**
     * The waiting for run task queue.
     */
    private PriorityBlockingQueue<AbsTask> mTaskQueue = null;

    private volatile boolean isExit = false;

    private Thread mThread = null;

    private TaskMain(String threadId) {
        this.threadId = threadId;
        this.init();
    }

    /**
     * 单一实例
     * 
     * @return
     */
    public static TaskMain getTaskEngine() {
        synchronized (lockInstance) {
            if (null == INSTANCE) {
                L.d(TAG, "Create a new TaskMain instance.");
                INSTANCE = new TaskMain(THREADID_DEFAULT);
            }
        }
        return INSTANCE;
    }

    private void init() {
        L.d(TAG, "TaskMain begain init..");
        mTaskQueue = new PriorityBlockingQueue<AbsTask>();
        sWorkQueue = new LinkedBlockingQueue<Runnable>(10);
        sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);

            public Thread newThread(Runnable r) {
                return new Thread(r, "task #" + mCount.getAndIncrement());
            }
        };

        sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                                           TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

        mThread = new Thread() {
            @Override
            public void run() {
                while (!isExit()) {
                    AbsTask mTask = getTask();
                    if (null == mTask) {
                        continue;
                    }
                    // at first, prepare for run task on the main thread
                    doPrepare(mTask);
                    sExecutor.execute(mTask);

                }
            }
        };
        mThread.setName(threadId);
        mThread.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
        mThread.start();
    }

    private AbsTask getTask() {
        L.d(TAG, "Get a task.");
        AbsTask mTask = mTaskQueue.peek();

        return mTask;
    }

    /**
     * Add a task to the taskEngine.
     * 
     * @param mTask
     *            The added Task
     * @return {@link TaskAddResult}
     */
    public final synchronized TaskAddResult addTask(AbsTask mTask) {
        return this.addTask(mTask, 0, TimeUnit.MILLISECONDS);
    }

    /**
     * Add a task to the taskEngine.
     * 
     * @param mTask
     *            The added Task
     * @param timeOut
     *            {@link Integer} timeOut lang time result.
     * @param timeUnit
     *            {@link TimeUnit}
     * @return {@link TaskAddResult}
     */
    public final synchronized TaskAddResult addTask(AbsTask mTask, int timeOut, TimeUnit timeUnit) {
        L.d(TAG, "Add a task.");
        TaskAddResult result = TaskAddResult.UNKOWN;
        boolean isOffer = false;
        try {
            isOffer = mTaskQueue.offer(mTask, timeOut, timeUnit);
        } catch (ClassCastException e) {
            e.printStackTrace();
            result = TaskAddResult.CLASSCASTERR;
        } catch (NullPointerException e) {
            e.printStackTrace();
            result = TaskAddResult.NULLPOINTER;
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            result = TaskAddResult.ILLEGALARGUMENT;
        }

        if (isOffer) {
            result = TaskAddResult.SUCCESS;

            // set the task status to STARTING
            mTask.setTaskStatus(TaskStatus.STARTING);
            // notify that this task will be working.
            mTask.onStart();
        }

        return result;
    }

    private boolean isExit() {
        return isExit;
    }

    /**
     * When call this, the task thread will be exited.
     */
    public void exit() {
        this.isExit = true;

        savaTask();

    }

    /**
     * Save the undone task.
     */
    // TODO 当程序退出或因异常需要关闭的时候,调用此方法保存未完成的任务
    private void savaTask() {

    }

    /**
     * Prepare run the task on the main thread.
     * 
     * @param mTask
     *            the prepare task
     */
    private void doPrepare(AbsTask mTask) {
        Looper.prepare();
        mTask.onPrepare();
        Looper.loop();
    }

    /**
     * An enum to show the status for add task into the taskengin
     * 
     * @author Karen [Email:keyu404@gmail.com]
     * 
     */
    public static enum TaskAddResult {
        /**
         * add status is unkown.default status.
         */
        UNKOWN,
        /**
         * add task success
         */
        SUCCESS,
        /**
         * cause a CastClassException
         */
        CLASSCASTERR,
        /**
         * cause a interruptedException
         */
        INTERRUPTED,
        /**
         * cause a NullPointerException
         */
        NULLPOINTER,
        /**
         * cause a IllegalargumentException
         */
        ILLEGALARGUMENT;
    }
}
